`timescale 1ps/1ps

//////////////////////////////////////////////////////////////////////////////////
// Company: 
// Engineer: JerryTech
// 
// Create Date: 2023/04/23
// Design Name: 
// Module Name: FIR_Filter
// Project Name: 
// Target Devices: 
// Tool Versions: 
// Description: FIR LPF
// 
// Dependencies: 
// 
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
// 
//////////////////////////////////////////////////////////////////////////////////

//Refer to https://www.runoob.com/w3cnote/verilog-fir.html


module jerrytech_fir #
(
    parameter IN_DATA_WIDTH = 14,
    parameter LOG2_STAGE =  4,//STAGE log2
    parameter EXPAND_WIDTH = 11,
    parameter OUT_DATA_WIDTH = 14,
    parameter STAGE      = 16 //PARAM Number is STAGE,real filter stage is STAGE - 1
)
(
    input  wire         sys_clk,
    input  wire         sys_rst,

    input  wire signed [IN_DATA_WIDTH - 1:0 ] data_in,
    input  wire         in_valid,

    output wire signed [OUT_DATA_WIDTH -1 :0 ] data_out,
    output wire         out_valid
);

reg [STAGE + 2:0 ] enable_reg;
reg signed [IN_DATA_WIDTH - 1:0 ] data_in_reg [0 :STAGE - 1]; 
reg signed [IN_DATA_WIDTH * 2 - 1:0 ] mul_out_reg [0 :STAGE - 1];
reg signed [IN_DATA_WIDTH - 1:0 ] h_param [0 :STAGE - 1];
reg signed [IN_DATA_WIDTH * 2 + LOG2_STAGE - 1:0 ] add_reg [0 :LOG2_STAGE ][0 :STAGE - 1];
reg [10:0 ] i;//Use for for count,cant synthesis.

initial begin
    $readmemh("fir_param.txt",h_param);//hex parameter file
end

always @ (posedge sys_clk) begin
    if(sys_rst)
        enable_reg <= 0;
    else begin
        enable_reg[0] <= in_valid;
        enable_reg[STAGE + 2:1] <= {enable_reg[STAGE + 1:1],enable_reg[0]};
    end
end

assign out_valid = enable_reg[STAGE + 2];

always @ (posedge sys_clk) begin
    if(sys_rst)
        for(i=0;i<STAGE;i=i+1) begin
            data_in_reg[i] <= 0;
        end
    else if(in_valid) begin
        data_in_reg[0] <= data_in;
        for(i=1;i<STAGE;i=i+1) begin
            data_in_reg[i] <= data_in_reg[i - 1];
        end
    end
end

always @ (posedge sys_clk) begin
    if(sys_rst)
        for(i=0;i<STAGE;i=i+1) begin
            mul_out_reg[i] <= 0;
        end
    else if(in_valid)
        for(i=0;i<STAGE;i=i+1) begin
                mul_out_reg[i] <= h_param[i] * data_in_reg[i];            
        end
end

//Binary tree adder 
//main function add_reg = mul_out_reg[0] + mul_out_reg[1] + mul_out_reg[2] + ...... + mul_out_reg[n] 
genvar u_stage, adder;
generate
  for( u_stage = 0; u_stage <= LOG2_STAGE ; u_stage=u_stage + 1 ) begin: u_stage_gen
    localparam ST_OUT_NUM = STAGE >> u_stage; 
    if( u_stage == 0 ) begin
        for( adder = 0; adder < ST_OUT_NUM; adder = adder + 1 ) begin: inputs_gen
            always @ (posedge sys_clk) begin
                if(sys_rst) begin
                    add_reg[u_stage][adder] <= 0;
                end
            else begin
                add_reg[u_stage][adder] <= mul_out_reg[adder];
            end
        end
        end
    end 
    else begin
        for( adder = 0; adder < ST_OUT_NUM; adder = adder + 1) begin: adder_gen
            always @ (posedge sys_clk) begin
                if(sys_rst ) begin
                    add_reg[u_stage][adder] <= 0;
                end 
                else begin
                    add_reg[u_stage][adder] <= add_reg[u_stage-1][adder*2] + add_reg[u_stage-1][adder*2+1];
                end
            end
        end
    end
end
endgenerate

assign data_out = {add_reg[LOG2_STAGE][0][IN_DATA_WIDTH * 2 + LOG2_STAGE - 1],add_reg[LOG2_STAGE][0][OUT_DATA_WIDTH+EXPAND_WIDTH - 2:EXPAND_WIDTH]};

endmodule

